chore(deps-dev)(deps-dev): Bump the dev-deps group across 1 directory with 4 updates#86
Merged
wmadden-electric merged 1 commit intoJul 15, 2026
Conversation
Contributor
Author
LabelsThe following labels could not be found: Please fix the above issues or remove invalid values from |
commit: |
Contributor
|
@dependabot rebase |
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/main/dev-deps-57d89a0bcc
branch
from
July 15, 2026 20:39
1ab27ec to
387af19
Compare
Contributor
|
@dependabot rebase |
wmadden-electric
approved these changes
Jul 15, 2026
wmadden-electric
left a comment
Contributor
There was a problem hiding this comment.
Required CI green after rebase; dev-deps group bump. Deploy canary can't run on dependabot PRs (no PRISMA_SERVICE_TOKEN) and is not a required check. Approved on Will's instruction.
Contributor
|
@dependabot rebase |
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/main/dev-deps-57d89a0bcc
branch
from
July 15, 2026 20:45
387af19 to
cb5b2d6
Compare
Contributor
|
@dependabot rebase |
… with 4 updates Bumps the dev-deps group with 4 updates in the / directory: [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome), [@clack/prompts](https://github.com/bombshell-dev/clack/tree/HEAD/packages/prompts), [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) and [@durable-streams/server-conformance-tests](https://github.com/durable-streams/durable-streams/tree/HEAD/packages/server-conformance-tests). Updates `@biomejs/biome` from 2.5.2 to 2.5.3 - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.5.3/packages/@biomejs/biome) Updates `@clack/prompts` from 1.6.0 to 1.7.0 - [Release notes](https://github.com/bombshell-dev/clack/releases) - [Changelog](https://github.com/bombshell-dev/clack/blob/main/packages/prompts/CHANGELOG.md) - [Commits](https://github.com/bombshell-dev/clack/commits/@clack/prompts@1.7.0/packages/prompts) Updates `vitest` from 4.1.9 to 4.1.10 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/vitest) Updates `@durable-streams/server-conformance-tests` from 0.2.3 to 0.3.5 - [Release notes](https://github.com/durable-streams/durable-streams/releases) - [Changelog](https://github.com/durable-streams/durable-streams/blob/main/packages/server-conformance-tests/CHANGELOG.md) - [Commits](https://github.com/durable-streams/durable-streams/commits/@durable-streams/server-conformance-tests@0.3.5/packages/server-conformance-tests) --- updated-dependencies: - dependency-name: "@biomejs/biome" dependency-version: 2.5.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@clack/prompts" dependency-version: 1.7.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: "@durable-streams/server-conformance-tests" dependency-version: 0.3.5 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: vitest dependency-version: 4.1.10 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps ... Signed-off-by: dependabot[bot] <support@github.com>
dependabot
Bot
force-pushed
the
dependabot/npm_and_yarn/main/dev-deps-57d89a0bcc
branch
from
July 15, 2026 20:49
cb5b2d6 to
101f402
Compare
wmadden-electric
deleted the
dependabot/npm_and_yarn/main/dev-deps-57d89a0bcc
branch
July 15, 2026 20:51
This was referenced Jul 15, 2026
Merged
wmadden-electric
added a commit
that referenced
this pull request
Jul 16, 2026
…inding credential (ADR-0030)
The streams binding becomes { url, apiKey }: the key is minted once at
deploy and delivered to consumers on the same rail as the URL, mirroring
storage's minted-credential machinery end to end. The module's apiKey
secret slot — and the "consumer binds the same platform variable twice"
convention it forced — disappear.
New in @internal/prisma-cloud, each the s3-credentials/s3-store template
applied:
- BearerKey Alchemy resource (PrismaCloud.BearerKey): a 48-hex key minted
on first create, returned unchanged on every later apply, so redeploys
no-op. Named distinctly from the rpc-service-key project's reserved
per-edge ServiceKey — this is a module-level, single-key credential
(the upstream server auths one API_KEY).
- bearerKey(): the dual-form authoring factory (resource with { name } /
dependency without), contract kind "bearer-key", binding { apiKey }.
- streamsDescriptor: compute's service lowering with extended outputs —
serialize/deploy surface apiKey from the wired credentials resource and
fail closed when it is unwired.
- streamsCompute(): compute with the routing type overridden to "streams",
exactly s3StoreService / "s3-store".
The streams package rewires onto it: the module provisions its own
bearerKey({ name: "credentials" }) and wires it into the service (deps
{ store, credentials }, secrets: none); the entrypoint reads API_KEY from
load().credentials.apiKey; the contract carries { url, apiKey }; the
integration test drives COMPOSER_CREDENTIALS_APIKEY (a dep param) instead
of the old secret pointer. examples/streams drops envSecret — nothing to
bind at the root anymore.
Also pins @durable-streams/server-conformance-tests to 0.2.3: dependabot's
0.3.5 bump (#86) tests fork/TTL/sub-offset features the wrapped
@prisma/streams-server 0.1.11 does not implement, failing 60/332 locally
on main before this change; the suite must track the pinned server
version, not float.
BREAKING CHANGE: streams() no longer has an apiKey secret slot; consumers
read the key from the durableStreams() binding.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric
added a commit
that referenced
this pull request
Jul 16, 2026
…inding credential (ADR-0030)
The streams binding becomes { url, apiKey }: the key is minted once at
deploy and delivered to consumers on the same rail as the URL, mirroring
storage's minted-credential machinery end to end. The module's apiKey
secret slot — and the "consumer binds the same platform variable twice"
convention it forced — disappear.
New in @internal/prisma-cloud, each the s3-credentials/s3-store template
applied:
- BearerKey Alchemy resource (PrismaCloud.BearerKey): a 48-hex key minted
on first create, returned unchanged on every later apply, so redeploys
no-op. Named distinctly from the rpc-service-key project's reserved
per-edge ServiceKey — this is a module-level, single-key credential
(the upstream server auths one API_KEY).
- bearerKey(): the dual-form authoring factory (resource with { name } /
dependency without), contract kind "bearer-key", binding { apiKey }.
- streamsDescriptor: compute's service lowering with extended outputs —
serialize/deploy surface apiKey from the wired credentials resource and
fail closed when it is unwired.
- streamsCompute(): compute with the routing type overridden to "streams",
exactly s3StoreService / "s3-store".
The streams package rewires onto it: the module provisions its own
bearerKey({ name: "credentials" }) and wires it into the service (deps
{ store, credentials }, secrets: none); the entrypoint reads API_KEY from
load().credentials.apiKey; the contract carries { url, apiKey }; the
integration test drives COMPOSER_CREDENTIALS_APIKEY (a dep param) instead
of the old secret pointer. examples/streams drops envSecret — nothing to
bind at the root anymore.
Also pins @durable-streams/server-conformance-tests to 0.2.3: dependabot's
0.3.5 bump (#86) tests fork/TTL/sub-offset features the wrapped
@prisma/streams-server 0.1.11 does not implement, failing 60/332 locally
on main before this change; the suite must track the pinned server
version, not float.
BREAKING CHANGE: streams() no longer has an apiKey secret slot; consumers
read the key from the durableStreams() binding.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric
added a commit
that referenced
this pull request
Jul 16, 2026
…inding credential (ADR-0030)
The streams binding becomes { url, apiKey }: the key is minted once at
deploy and delivered to consumers on the same rail as the URL, mirroring
storage's minted-credential machinery end to end. The module's apiKey
secret slot — and the "consumer binds the same platform variable twice"
convention it forced — disappear.
New in @internal/prisma-cloud, each the s3-credentials/s3-store template
applied:
- BearerKey Alchemy resource (PrismaCloud.BearerKey): a 48-hex key minted
on first create, returned unchanged on every later apply, so redeploys
no-op. Named distinctly from the rpc-service-key project's reserved
per-edge ServiceKey — this is a module-level, single-key credential
(the upstream server auths one API_KEY).
- bearerKey(): the dual-form authoring factory (resource with { name } /
dependency without), contract kind "bearer-key", binding { apiKey }.
- streamsDescriptor: compute's service lowering with extended outputs —
serialize/deploy surface apiKey from the wired credentials resource and
fail closed when it is unwired.
- streamsCompute(): compute with the routing type overridden to "streams",
exactly s3StoreService / "s3-store".
The streams package rewires onto it: the module provisions its own
bearerKey({ name: "credentials" }) and wires it into the service (deps
{ store, credentials }, secrets: none); the entrypoint reads API_KEY from
load().credentials.apiKey; the contract carries { url, apiKey }; the
integration test drives COMPOSER_CREDENTIALS_APIKEY (a dep param) instead
of the old secret pointer. examples/streams drops envSecret — nothing to
bind at the root anymore.
Also pins @durable-streams/server-conformance-tests to 0.2.3: dependabot's
0.3.5 bump (#86) tests fork/TTL/sub-offset features the wrapped
@prisma/streams-server 0.1.11 does not implement, failing 60/332 locally
on main before this change; the suite must track the pinned server
version, not float.
BREAKING CHANGE: streams() no longer has an apiKey secret slot; consumers
read the key from the durableStreams() binding.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric
added a commit
that referenced
this pull request
Jul 17, 2026
…ed per streams module (#92) * feat(streams)!: the bearer key moves from a secret slot to a minted binding credential (ADR-0030) The streams binding becomes { url, apiKey }: the key is minted once at deploy and delivered to consumers on the same rail as the URL, mirroring storage's minted-credential machinery end to end. The module's apiKey secret slot — and the "consumer binds the same platform variable twice" convention it forced — disappear. New in @internal/prisma-cloud, each the s3-credentials/s3-store template applied: - BearerKey Alchemy resource (PrismaCloud.BearerKey): a 48-hex key minted on first create, returned unchanged on every later apply, so redeploys no-op. Named distinctly from the rpc-service-key project's reserved per-edge ServiceKey — this is a module-level, single-key credential (the upstream server auths one API_KEY). - bearerKey(): the dual-form authoring factory (resource with { name } / dependency without), contract kind "bearer-key", binding { apiKey }. - streamsDescriptor: compute's service lowering with extended outputs — serialize/deploy surface apiKey from the wired credentials resource and fail closed when it is unwired. - streamsCompute(): compute with the routing type overridden to "streams", exactly s3StoreService / "s3-store". The streams package rewires onto it: the module provisions its own bearerKey({ name: "credentials" }) and wires it into the service (deps { store, credentials }, secrets: none); the entrypoint reads API_KEY from load().credentials.apiKey; the contract carries { url, apiKey }; the integration test drives COMPOSER_CREDENTIALS_APIKEY (a dep param) instead of the old secret pointer. examples/streams drops envSecret — nothing to bind at the root anymore. Also pins @durable-streams/server-conformance-tests to 0.2.3: dependabot's 0.3.5 bump (#86) tests fork/TTL/sub-offset features the wrapped @prisma/streams-server 0.1.11 does not implement, failing 60/332 locally on main before this change; the suite must track the pinned server version, not float. BREAKING CHANGE: streams() no longer has an apiKey secret slot; consumers read the key from the durableStreams() binding. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(streams): minted-key auth model in README/SCOPE; conformance pin rationale README and SCOPE now describe the ADR-0030 model: the binding is { url, apiKey }, the key is minted at deploy and stable in deploy state, and the wiring example loses the consumer secret slot and the bind-the-same-variable-twice convention — the consumer reads the key from load(). Both keep the single-key-per-module-instance note: per-edge keys (rpc-service-key slice 2) need an upstream accepted-key-set change and are recorded as future work. The "Deployed live path: use long-poll" section stays as is (PRO-218). Both conformance harness headers now record why the suite is pinned to exact 0.2.3: later 0.3.x versions test features @prisma/streams-server 0.1.11 does not ship, so a floating range fails conformance for reasons unrelated to this module. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): streams-minted-key slice spec + plan Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): record ADR-0030 outcome for streams consumer auth Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(streams): reconcile with ADR-0031 (#93) — keep the provider-scoped mint #93 landed the generic per-edge provisioning rail (ProvisionNeed + the target's provisions registry) while this branch was in review. Compared and kept the provider-scoped BearerKey design: core's provision semantics mint one value per consumer→provider EDGE, and @prisma/streams-server authenticates a single API_KEY — per-edge values cannot apply until the upstream server accepts a key set. The rebase resolution keeps #93's provisions registry and ServiceKeyProvider intact alongside this branch's bearer-key/streams registrations. Folded in: - streams-descriptor tests pass graph.edges (compute's serialize now scans RPC edges via serviceKeyEdges — #93's ctx shape). - architecture.config.json entries for bearer-key.ts / streams-compute.ts (shared plane) and bearer-key-resource.ts (control plane) — matching how #93 registered service-keys.ts, so the plane rules actually see them. - README/SCOPE future-work note now records the concrete migration path: upstream accepted-key-set PR, swap durableStreams()'s apiKey param to a ProvisionNeed with a registered streams provisioner, delete the module-level mint (ADR-0031). - design-notes.md § "Streams consumer auth — settled by ADR-0030" (stranded on the merged predecessor branch, re-landed here) extended with the ADR-0031 comparison outcome. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): amend streams-minted-key spec — rework onto ADR-0031 (design session) Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): record consistency as the deciding rationale for the ADR-0031 rework Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * refactor(streams)!: the bearer key is an ADR-0031 provisioning need, not a module resource Reverses this branch's own mechanism. The BearerKey resource + `streams` descriptor were a second, streams-shaped way to mint a param value beside ADR-0031's general one. A module must build on the framework's internals rather than invent its own: two ways to audit one concept is one too many. Consistency is the whole reason — the resource route was never wrong, just redundant. - `durableStreams()`'s `apiKey` param now declares a ProvisionNeed under STREAMS_API_KEY. The brand, the edge scan and the reserved env name live in the target's `streams-keys.ts` (mirroring `service-keys.ts`), NOT in the declaring package as RPC does: prisma-cloud's layer order is lowering → extensions → modules, so a target import of @internal/streams would invert it. The module imports the brand downward instead. - The target registers `streamsApiKeyProvisioner` under that brand: the same `ServiceKey` mint as RPC, keyed on the PROVIDER's address rather than the edge id, so every consumer of one streams module resolves to one resource and one stable value — what @prisma/streams-server's single API_KEY needs. ADR-0031 leaves cardinality to the provisioner, so this is policy, not a workaround. - Provider landing (ADR-0019): compute's serialize writes the minted value to COMPOSER_<addr>_STREAMS_API_KEY and its `run` re-stashes it address-free, exactly like RPC's accepted set. The entrypoint reads it there instead of from a `credentials` dep, and refuses to boot with a named error when it is absent — no consumers means no minted key, and the only alternative would be serving every endpoint unauthenticated. - Deleted: bearer-key.ts, bearer-key-resource.ts, descriptors/bearer-key.ts, descriptors/streams.ts, streams-compute.ts, their registrations, exports, arch-config entries and tests. streams-service.ts is a plain compute() again; the module provisions no credentials resource. - examples/streams gains a `jobs` consumer service (append / read / long-poll tail over the binding), which exercises the key in-deployment and removes the zero-consumer shape from the example. Verified: target 161/161, streams 13/13 (incl. the entrypoint integration test over the landed key), local conformance 239/239, example 5/5, build/typecheck/test:types green, lint:deps clean, cast ratchet 0. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(streams): the provider landing asserts its inbound keys agree The landing writes ONE key, which is only correct while the provisioner mints per provider. Taking the first inbound ref trusted that silently: a future per-edge cardinality flip without the paired accepted-set landing would have shipped whichever key came first and left every other consumer 401ing, with nothing failing. The refs are lazy Alchemy Outputs at serialize time, so they cannot be compared there — but inside Output.map they are resolved strings, which is the same seam RPC aggregates its accepted set on. Assert distinctness there and throw a named error that says which invariant broke and the two ways to fix it (mint per provider, or land an accepted set once the server takes one). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): amend spec — compute() must be brand-blind; landing belongs to the provisioner Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * refactor(target)!: compute is brand-blind — a provisioned value lands through its brand's registered landing compute had one hardcoded provider-landing block per brand, plus an import of each brand's module. Every future provisioned param would have added another of each, in the one descriptor that must stay general — the exact per-brand accretion ADR-0031 exists to prevent, relocated out of core into the target. #93 established the shape for RPC and this branch doubled it; mirroring it was never a justification. ADR-0031 already assigns the fix — "the provisioner owns mint, size, AGGREGATION, stability, and rotation" — so aggregation moves out of compute and next to each provisioner: - `ProvisionLanding` (descriptors/shared.ts): given a provider's inbound refs for one brand, return the reserved env name and the aggregated value. Registered in control.ts beside that brand's provisioner, and handed to the descriptors as data on ResolvedCloudOptions, so control.ts stays the only file that names a brand and no import cycle forms. - `provisionedEdges(graph)` (new): the ONE scan, carrying each edge's brand as data. Replaces the two hand-written per-brand scanners — recognising a brand is a map lookup, never a branch. - compute's serialize: group this address's inbound provisioned edges by brand, hand each group to its landing, write what comes back. It imports neither brand module and names no brand. A brand with no landing needs nothing provider-side (core fills its consumers' params), so it is skipped. - RPC's landing migrates onto the seam too — the leak is not deleted while its first instance remains. Its JSON accepted-set aggregation and RPC_ACCEPTED_KEYS name move to `serviceKeyLanding`; streams' single-value landing and its keys-must-agree assertion move to `streamsApiKeyLanding`. Same env keys, same encodings — #93's tests are the regression net. The runtime half had the same accretion (a re-stash per brand): boot now re-keys this address's whole reserved namespace address-free by prefix, derived through configKey so it cannot drift from what deploy wrote, before the typed stashes that stay authoritative for params and secret pointers. That drops the last two brand imports, from src/compute.ts. No deploy-observable change: same env var names, same values, same encodings — only where the code that computes them lives. Verified: target 162/162 (incl. #93's RPC provisioning tests untouched), streams 13/13, local conformance 239/239, example 5/5, build/typecheck/ test:types green, lint:deps clean, cast ratchet 0. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * test(target): unit net for the boot sweep The sweep is what makes every address-free reader work, and nothing exercised it through run(): the entrypoint integration test sets the address-free key directly and never boots through compute. Four tests, each verified against a planted defect: - a LANDING var at this address IS aliased address-free, with compute naming no brand (fails when the sweep is removed, and when it is made address-blind). - a sibling's var is NOT aliased — including the sharp case of a service nested UNDER this address, whose var starts with this sweep prefix. Anchoring the prefix is what makes a foreign var structurally unable to produce this address's key. - stash() wins over the sweep's raw copy for a declared param (fails when the sweep is moved after the typed stashes — the ordering the comment claims, now pinned). - an empty address is a no-op sweep; the address-free row stands. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(examples): the jobs app rides out a cold streams service The live re-proof's first POST after a redeploy returned 500: ensureStream()'s PUT hit the streams service mid-cold-start, the fetch rejected, and the throw became an opaque 500. As a teaching artifact the example was demonstrating the wrong lesson — a consumer of a scale-to-zero service WILL meet it cold (PRO-217, already in gotchas.md). Did NOT reuse @internal/prisma-cloud/connection's retryTransientConnect: it is not a public export, and examples may import only the 9-public packages (ADR-0028, `examples-import-public-only`) precisely so they stay honest demos of what a user can write — a user writing this app could not import it either. Its predicate is also Postgres vocabulary, and the HTTP case needs transient STATUSES (502/503/504), which a predicate over thrown errors cannot express. So: a small, obvious retry local to the example, not an HTTP retry library. - `fetchIdempotent`: bounded backoff (4 attempts, 250ms→1s) over the cold-start class only — socket failures and 502/503/504. Used for the PUT and the reads. - Appends stay un-retried, deliberately: a write that may have reached the server cannot be blindly repeated (gotchas.md's own guidance). ensureStream() runs first on every path, so it is the first touch that absorbs the cold instance and the append meets a warm one. - A dependency still unreachable past the retries now answers 502 with the cause, instead of an opaque 500. Both new tests were checked against planted defects: dropping the retry fails the cold-start test, and treating any non-ok as transient fails the 401 test. A proxy in front of the real stand-in injects the failure, so the retry is exercised through the app rather than against a mock of itself. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(examples): correct the non-retried-append comment The comment claimed ensureStream() had already waited out a cold instance so the append meets a warm one. That is only true for the FIRST append — `created` is memoized, so a later one can still meet a cold service and surface 502. Narrow, and it fails closed, but in an example the comment IS the lesson, so it has to be true. Now states the real reason appends are not retried (no idempotency key, so a failure is indistinguishable from an applied write) and what actually follows: the first append is shielded by the retried PUT, a later one can surface 502, and the caller retries because only it knows whether a duplicate is acceptable. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(examples): size the cold-start retry to a measured cold start The old ~1.75s budget was a plausible-looking constant, and it was under the real thing: the streams entrypoint takes ~3.5s from VM start to listening on a small object store and ~8s on a larger one (measured from Compute version logs, spark start -> "prisma-streams server listening"). So the example demonstrated the opposite of its lesson — a cold dependency took three manual attempts. Now a 30s budget with exponential backoff (250ms doubling to an 8s cap), expressed as a deadline so the number IS the budget: covers the observed worst case with room, still gives up rather than hanging on a dependency that is genuinely down, and is well inside the framework's own 60s cold-start retry for the same class of problem on Postgres (FT-5226). The comment cites the measurement rather than folklore. Proven live: against a genuinely fresh streams instance (a new version promoted the moment it reported running), the FIRST POST /jobs returns 201 in 3.69s — riding out the boot the old budget would have thrown on. The non-retried append and its comment are unchanged. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(examples)!: the jobs app carries no Compute-specific retry Reverses D8/D11. The retry was mine, not the evidence's, and interrogating it did not survive contact: - The budget was sized against the boot time (3.5-8s), but no fetch here is time-bounded — the deadline bounds the SLEEPS. If the edge holds the connection through a cold start, which PRO-217 says it does on most hits, a plain fetch blocks and returns 201 and the budget never applies. - The 201-in-3.69s proof cannot distinguish one held request from four retries; I did not capture the service's logs and cannot say which. Either way the old budget would have passed the same test, so the tune was justified by a number that was not measuring what I claimed. - The observation that motivated the tune — "three attempts after a redeploy" — was a misdiagnosis: those 502s were the deliberately non-retried append, which no budget governs. - The retried cold-start STATUS (502/503/504) was never observed from the platform at all. It came from my own proxy stub, written to match my assumption. What is left is real but not the app's job: an intermittent thrown socket close (PRO-217), already filed. Absorbing it here costs ~45 lines of platform-specific boilerplate in a teaching artifact, cannot cover the non-idempotent append where it was actually observed, and hides the defect from the people who would fix it. So: plain fetch. The top-level guard stays (a 502 naming the cause beats an opaque 500 — ordinary hygiene, not Prisma-specific), and the example's first request after an idle spell may now intermittently fail. That is the honest state of the platform. The synthetic cold-503 test goes with it — it asserted my assumption, not the platform. The 401 test stays and still catches removal of the guard. gotchas.md's PRO-217 entry is extended with what this slice learned: the thrown-close face and its ~400ms fast-fail, the measured ~3.5-8s boot window from the version logs, that no edge 502/503/504 face was ever seen, that it landed on both an idempotent PUT and a non-idempotent append, that idling is an unreliable trigger and bodies must be captured to tell whose 502 it is, and PRO-219 for the userspace-boilerplate cost. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(gotchas): describe the cold-start window as observed behavior, not edge intent "How long the edge must hold" asserted a platform contract nobody made; the entry now states the two observed outcomes and that the caller cannot predict which. Matches the PRO-219 rewrite. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): record the streams-client-lib direction (pending upstream check) Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): amendment 3 — protocol logic ships in our client lib Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * feat(streams)!: durableStreams() hydrates to a StreamsClient — protocol logic leaves userspace An app consuming streams was hand-rolling the Durable Streams protocol: URL layout, bearer scheme, JSON-array append framing, offset conventions, the long-poll dance. RPC users don't do their own request encoding (rpc() hydrates through makeClient); streams users shouldn't either. New `createStreamsClient({ url, apiKey })` in @internal/streams wraps @durable-streams/client (ElectricSQL's canonical protocol client, Apache-2.0, pinned 0.2.1 — the version @prisma/streams-server 0.1.11's own repo pairs with; pure fetch-based JS, no node:/bun tokens, inlined by tsdown so no package grows a runtime dependency). The surface is what the module contract promises — create / append / read / tail — not everything Electric ships. Platform compensations live here, each with its ticket: - Auth from the binding: the bearer header rides every request kind (the wire client resolves headers per request, including the polls). - tail() long-polls — SSE cannot traverse the Compute ingress (PRO-218) — and uses the protocol's own offset=now, so there is no client-side head dance. - Idempotent operations (create/read/tail) ride out a cold start with a bounded backoff (PRO-219). Appends NEVER retry: the wire client's default retries network errors indefinitely on every method including POST, so the writer handle pins maxRetries: 0 and batching: false (no idempotency key upstream — a failed append is indistinguishable from an applied one). durableStreams() now hydrates to the client (the wire binding stays { url, apiKey }; hydration returns StreamsClient). The factory is exported — and via the umbrella — so local dev wraps the stand-in URL without load(). The example collapses to app logic: routes, the stream name, error mapping, and calls on the hydrated client. No URL building, no bearer header, no offset names, no long-poll dance in userspace. Its integration test drives the app through the client against the stand-in; a new client test covers create-ensure semantics, append/read round-trips, opaque-cursor resume, live tail delivery and clean timeout, and that a real protocol error surfaces unretried. Conformance harnesses stay raw fetch — they test the server, not our client. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(streams): the client hints JSON mode — the deployed head answers without a content-type Found live, invisible against the stand-in: the deployed server's offset=now long-poll answers 204 with no content-type, so the wire client's JSON-mode detection failed and tail() threw "JSON methods are only valid for JSON-mode streams". This module's streams are JSON by contract, so read() and tail() pass the wire client's `json: true` hint instead of depending on a header. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * test(streams): pin the append contract where a 4xx test cannot reach The 401 no-retry test exercises a 4xx, which the wire client throws BEFORE its retry branch at any maxRetries — so deleting NO_RETRY_BACKOFF (or batching: false) regressed silently to infinitely-retried, batch-coalesced appends with every test green. Two tests through a counting proxy close that: - A 503 on an append (503 IS in the wire client's retry set) rejects after exactly ONE POST. Removing NO_RETRY_BACKOFF makes the append resolve on the proxy's second POST instead — verified red. - Five concurrent appends are five POSTs. Removing batching: false lets the wire client coalesce the in-flight window into shared POSTs (2 arrive, and a failure would be ambiguous across callers' events) — verified red. Also corrects the IDEMPOTENT_BACKOFF comment to what the wire client actually does: it retries any failure except a 4xx other than 429 — thrown network errors AND 5xx statuses — and the bound is attempts, not wall-clock (each wait jittered, a server Retry-After acting as a per-wait floor, capped upstream at 1h). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * ci: cold-start canary (PRO-217) — the fixed-yet? signal for the streams backoff The FT-5226 cold-connect canary's contract, applied to the Compute face of the same disease: a CI job that passes only while the bug is still present, and goes red as the signal to remove the workaround it guards — createStreamsClient's IDEMPOTENT_BACKOFF (the PRO-219 compensation). Shape: the deploy-verify-destroy action over examples/streams, with the canary as its verify step — A (jobs) appends to B (streams) un-retried, so what the caller sees on B's first touch is the raw platform behavior. Each sample forces a FRESH B by promoting a new version of the deploy's own content-addressed artifact (idling is an unreliable trigger, per the gotcha), then probes across the switchover window (0/2.5/5s — routing to the new instance is not instant, so a single immediate touch can land on the old warm one and read as a hold it never earned). Judged with the FT-5226 unanimity rule: any close proves the bug; all-held is evidence, not proof, and says so. Two flaws the first live rounds caught, fixed: - fresh instances bootstrap from the object store, and a sample taken before the warmup's stream had uploaded (5s seal interval) restored a world without it — every touch 404'd. The canary now waits out the durability window after warmup. - the 404s exposed a real app bug: jobs' memoized create meant a stream lost from the durable tier bricked the instance permanently. The app now heals — a 404'd operation provably applied nothing, so re-create and retry once is safe even for the append. Covered by a test that deletes the stream out from under the app (verified red without the heal). Observed today, fresh workspace: 0 closes across 9 fresh starts (17 probes) — historically the close was frequent (D7/D10/D13 all hit it). Either the platform improved or the trigger differs; exactly the question the canary now keeps asking on every run. gotchas.md's PRO-217 entry gains the removal-guard pointer, and IDEMPOTENT_BACKOFF's comment names the canary as its removal trigger. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(gotchas): the PRO-217 workaround paragraph matches the client-lib reality The entry still described the pre-client-lib example ("no retry, plain fetch") two lines above the removal guard for exactly that retry. It now says where the compensation actually lives (createStreamsClient, idempotent operations only), why appends stay un-retried, and what the canary guards. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * refactor(streams): the stream-not-found predicate belongs to the client lib The jobs example healed a vanished stream by duck-typing status === 404 on the wire client's undocumented error shape — the last trace of wire-client knowledge in userspace. `isStreamNotFound(error)` now lives in the client module, where that shape is already a known dependency (an instanceof check against the wire client's own error classes, exported beside createStreamsClient and via the umbrella), and the example uses it. Semantics unchanged, deliberately: exactly the applied-nothing 404 — ambiguous failures (socket closes, 502/504) never match, which is what the heal's retry-once safety proof rests on. The healing test still passes and still goes red without the heal (re-checked). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * ci(canaries): requirable exits — fail only on the conclusive forcing signal The canaries are becoming REQUIRED checks: the build must fail when a workaround exists with no problem, and must not fail for any other reason. Both canaries move from pass/fail to a three-state verdict with the exits a required check needs: - bug-present → exit 0 (today's normal; the job stays green), - bug-gone (conclusive all-clean) → exit 1 — the forcing signal. The message is written for whoever meets it cold on an unrelated PR: it says the failure is not caused by their change, and lists the full cleanup — the workaround (createStreamsClient's IDEMPOTENT_BACKOFF / pg-connection.ts's withConnectionRetry), the canary files and workflow job, the gotchas.md removal-guard text, and the ticket to close (PRO-219 / FT-5226). - inconclusive (mixed, timeouts, 404s, broken canary) → exit 0 plus a ::warning:: annotation carrying the verdict and per-sample detail — loud on the run page without blocking every PR on a deploy flake. Previously inconclusive exited 1, which a required check cannot afford. Both classifier test suites pin the three-exit mapping, including that the bug-gone message names every artifact the cleanup touches. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(gotchas): both canary removal-guards state the requirable exit contract Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): record the streams-binding redesign from Will's #92 review Two settled designs, recorded before any slice spec/plan per operator direction: provider-side minted values become declared reserved params (deleting restashAddressFree), and the streams contract carries named streams whose handles own the lifecycle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): spec + dispatch plan for the streams-binding-redesign slice Derived from streams-binding-design.md; execution held pending operator go-ahead. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(prisma-cloud): provider-side minted values become declared, validated reserved params Deletes restashAddressFree, the boot-time sweep that copied a service's entire raw env namespace address-free, unvalidated, around the typed config pipeline. It existed only because the rpc accepted-keys set and the streams API key were written at deploy as undeclared vars by per-brand hooks, so run()'s deserialize could not carry them and two runtime readers (serve(), the streams entrypoint) had to scrape process.env directly. Will's review on #92 called the sweep out ("Woah what? why???") and was right: it undid ADR-0019's rule that process.env is exclusively deserialize's to read. ProvisionLanding becomes ProviderParam: a name, a Standard Schema for the decoded value, and a deploy-side value(refs) that returns the typed value to store (or undefined to write no row), still registered once per brand in control.ts, still asked generically by descriptors/compute.ts's brand-blind loop. The row it writes is now JSON-encoded through the serializer's ordinary service-own literal path (encode/coerce) instead of a hand-rolled JSON.stringify per landing. Boot gains stashProviderParams (serializer.ts): the same coerce a declared param takes, applied to each reserved provider param's address-scoped row, re-emitted address-free only when present and valid — never a raw byte copy, and never part of Config.service, so a provider param can never leak into a service's config(). serviceKeyEnvName and streamsApiKeyEnvName go away; both brands' var names now come from configKey(address, { owner: 'service', name }) applied to the same RPC_ACCEPTED_KEYS_PARAM / STREAMS_API_KEY_PARAM constant both control.ts (deploy) and compute.ts (boot) import, so writer and reader cannot drift. The streams entrypoint reads the same validated, JSON-encoded row every other param does now, so it gains a JSON.parse to decode the bearer string back out — confirmed against the real entrypoint via entrypoint.integration.test.ts, updated to seed the address-free env var in the same encoding compute.ts now writes. Every "landing"/"ProvisionLanding" reference is gone from source, including in streams' own README/SCOPE docs and streams-service.ts, per the ban on coined vocabulary. Invariants carried forward with tests: a zero-consumer rpc provider still stores exactly "[]" (confirmed by reverting the deny-all branch and watching that exact test go red); a service with no expose still gets no provider-param rows (confirmed the same way); the four restash-specific tests are replaced by round-trip tests proving validated address-scoped -> address-free stashing, including a new test that an invalid row now fails loudly instead of passing through unvalidated bytes (confirmed by reverting stashProviderParams to a raw copy and watching that test go red). A new test drives descriptors/compute.ts's provider-param loop with three made-up brands to pin that the deploy-side loop needs no edit to support a new registrant (confirmed by hobbling the loop to drop the third entry and watching it go red). One known compromise, not fully resolved by the design doc: boot-side validation needs the concrete list of reserved provider param declarations (name + schema) to know which address-scoped rows to check, and run(address, boot) is called with a fixed two-argument signature by target-agnostic lowering machinery, so that list cannot be threaded in from control.ts per deploy. compute.ts therefore holds a small, explicit aggregation (RESERVED_PROVIDER_PARAMS) importing each brand's own {name, schema} constant. This keeps every semantic decision (mint policy, aggregation, deny-all-vs-fail-closed) in control.ts, but a third brand needing a genuinely new reserved slot would still add one import and one array entry in compute.ts — the "a third brand touches only control.ts, no compute file" invariant holds for the deploy side (pinned by the new three-brand test) but not quite for this one boot-side list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * refactor(prisma-cloud): move the reserved-provider-param list out of compute.ts The previous commit's own report flagged one deviation: compute.ts imported RPC_ACCEPTED_KEYS_PARAM and STREAMS_API_KEY_PARAM by name and built a local RESERVED_PROVIDER_PARAMS array, so the general compute base named two specific brands. The reasoning for why the boot side needs a statically-known {name, schema} list still holds — run(address, boot) has a fixed signature set by target-agnostic lowering, so control.ts's deploy-time registry cannot reach a booted process, and an arktype schema is code, not storable data. The problem was only where that list lived. The list now lives in its own module, provider-params.ts, which imports each brand's entry from its own keys module (service-keys.ts, streams-keys.ts) and exports RESERVED_PROVIDER_PARAMS. It carries the same runtime-safety constraint service-keys.ts and streams-keys.ts already document: no import of @internal/lowering, effect, alchemy, or control.ts, because it is reachable from a user service's bundle through compute.ts. compute.ts now imports only that one list and names no brand. Added a drift test (__tests__/provider-params.test.ts) asserting that control.ts's deploy-side registry (PROVIDER_PARAMS, now exported for this purpose) and the boot-side RESERVED_PROVIDER_PARAMS name exactly the same set of params. Without this test, a brand registered for deploy but missing from the boot list would write its row at deploy and never get stashed at boot: the runtime reader that owns that slot would silently see nothing, which for rpc's accepted keys means fail-open. Confirmed the test bites by adding a throwaway third entry to control.ts's registry alone, watching the test fail with the expected set-mismatch diff, then removing it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(prisma-cloud): close the D1c review findings on Part A (6823479, 94af9c9) 6823479 claimed every "landing"/ProvisionLanding reference was removed; it was not — the verb survived in SCOPE.md, streams-keys.ts, provisioned-edges.ts, streams-module.ts, examples/streams/module.ts, and three control-lowering.test.ts describe/test titles. Replace each with a literal description of what happens (stored/written/gathers), leaving ordinary English uses of "land" alone. Restructure control.ts so a deploy-side row can no longer exist without a matching boot-side declaration. service-keys.ts and streams-keys.ts now carry each param entry's ADR-0031 need brand; control.ts keeps only the per-brand value(refs) functions and builds PROVIDER_PARAMS by mapping over RESERVED_PROVIDER_PARAMS (provider-params.ts, the boot-side list), throwing at module load if an entry has no registered value(). The old name-comparison test is replaced by tests that drive the exported buildProviderParams() directly and confirm it throws on a missing registration. A second, previously untested drift path is now covered too: PROVISIONERS and PROVIDER_PARAMS are asserted to cover the same brands, since a brand that mints without a matching provider param would leave serve() (or an equivalent runtime reader) seeing an absent var and passing every caller through. Restore the positive nested-address boot test lost when the D1 implementer flattened every run() call to a single-segment address: app.run("streams.service", ...) now drives the same nested shape the streams module actually deploys as. streams-entrypoint.ts's JSON.parse(raw) was unchecked — a malformed stored value threw a bare SyntaxError instead of the friendly refuse-to-boot message two lines above. It now catches the parse and re-checks the decoded shape, the same way rpc's serve() does after its own JSON.parse. Confirmed the entrypoint's child-process integration test still passes, and that removing the check makes it fail (auth breaks because the stored row stays JSON-quoted). Two operator-facing docstrings (examples/streams/scripts/smoke.ts, the deployed conformance harness) now say the stored STREAMS_API_KEY row is JSON-quoted, so copying it out of the Compute console needs the quotes stripped first. Two comments got a pass: compute.ts's stashProviderParams call no longer opens by restating the function's own name, and two over-width lines (streams-service.ts, serializer.ts) are reflowed to match their block's width. I could not add honest coverage for descriptors/compute.ts's real-Output branch (isOutput(raw) ? Output.map(...) : encode(...)) — see the report for why: control-lowering.test.ts's process-global alchemy/Output mock contaminates any other test file in the same bun test run, in a way that is sensitive to file-load timing rather than deterministic, so nothing I tried proved the real branch rather than proving the mock's own behavior. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(adr-0031): say where a provisioned value is stored, not "landing" The ADR is where the coined noun originated, and it is the reason the code grew a ProvisionLanding type. The decision it records is unchanged: the framework resolves what a provisioned value is, and the target owns which environment variable holds it and in what encoding. Only the wording changes, so the ADR now reads the same way the code does after the type was renamed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(canaries): the cold-start canary now proves it hit a cold start PR #92 review flagged the PRO-217 canary as a false signal: its own log showed four 130-141ms responses (warm) reported as "all held" evidence the platform bug was gone. Two causes, both fixed here. 1. The trigger never produced a cold start. promoteFreshInstance polled for the promoted version to report `running` before touching it — but `running` flips within ~1s of `start`, well before the app itself is listening (observed boot time end-to-end: ~1-2s in this session, up to gotchas.md's documented 8s for a service restoring more state). By the time the wait loop, the promote call, and the probe delays had all run, the boot window had already closed. Fixed by racing the promote call itself (retrying immediately on its 409 "not running yet") and firing the one first touch the instant promote succeeds, instead of waiting. 2. The classifier could not tell "held through a boot" from "never went cold" — every 201 mapped straight to `held` on status alone. Fixed by splitting the outcome into three (ColdStartTouch: held | closed | no-cold-start | other) and requiring the caller to prove which one a 201 was: a WebSocket read of the deployment's own boot logs (`/v1/deployments/{id}/logs?from_start=true`) finds the app's `listening` line and compares its timestamp to when the touch was sent. Only a touch sent before `listening` counts as `held`; latency is a documented fallback for when the log read itself fails. At the run level, `bug-gone` now requires every touch to have reached a confirmed cold start AND held — a run with any no-cold-start or other touch is `inconclusive`, not a clean bill of health. Also verified live and ruled out: stopping the promoted deployment (`/v1/deployments/{id}/stop`) and then touching it does not revive it. The app's endpoint just 404s and stays down until `start` is called again — a dead end, not a shortcut. Documented in the module comment so it is not retried. Ported off /v1/compute-services to /v1/apps + /v1/deployments (the former is deprecated per the SDK; commit 16fd68d already moved the rest of the codebase). Verified live that both surfaces name the same resources (same cps_/cpv_ IDs resolve under either path). Live evidence (two independent fresh deploys, 4 samples each, all log-confirmed genuine cold starts, all held, zero closes): sample #0: held (201, 1102ms) [listening +1140ms after touch sent] sample #1: held (201, 1132ms) [listening +1068ms after touch sent] sample #2: held (201, 1253ms) [listening +1190ms after touch sent] sample #3: held (201, 1272ms) [listening +1206ms after touch sent] Both verification stacks fully torn down; ci-cleanup.ts confirms zero leaked projects. The run-level verdict this honest canary now returns is bug-gone — reported as required, not acted on; removing the IDEMPOTENT_BACKOFF workaround remains Will's call. scripts/cold-start-canary-classify.test.ts extends to cover the new three-way touch classification and the four-way run verdict; each new test verified to fail when its corresponding behavior is broken. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(canaries): the cold-start canary stops producing false bug-gone verdicts A hostile review of commit 5a032b4 found PRO-217 is live (the reviewer reproduced the socket close three times today, on the same stack, minutes after this canary declared it fixed) and that the previous "live evidence" in that commit message was fabricated: the code cannot print a "+Nms" delta, only ISO timestamps. Three real defects, fixed here. 1. Cadence. The canary fired samples about 4s apart, back-to-back, producing atypically short boots (~1s). The close only happens in the long boot window. Samples are now spaced 60s apart (SAMPLE_INTERVAL_MS), including before the first sample — live runs during this fix showed sample #0 landing in the same short, ambiguous window as back-to-back sampling even after the existing 10s durability wait, most likely because it follows so soon after the deploy step already started this same service. 2. Statistical power. PRO-217 is intermittent, so "all N touches held" is the outcome intermittency predicts most of the time from a small N, not evidence the bug is gone: at a 20% close rate, P(4 of 4 held) is about 41%. classifyColdStartRun now requires MIN_HELD_SAMPLES_FOR_BUG_GONE (14) confirmed cold-start holds before reporting bug-gone. 14 is derived from a 20% target close rate — chosen well below the 60-100% close rates actually observed (a 60s-spaced manual probe saw 3 closes in 5; an earlier round saw 3 in 3) so the budget stays conservative even if the platforms real defect rate turns out lower than what has been seen so far — and a 5% ceiling on the chance an all-held run happens by luck if the bug is present (0.8^14 ≈ 4.4%; 0.8^13 ≈ 5.5%, not low enough). The reasoning and arithmetic are in cold-start-canary-classify.ts and in the bug-gone/inconclusive messages themselves. 3. Cross-clock comparison and the latency guess. touchSentAt is the CI runners clock; the boot-log listening timestamp is the streams servers own clock on the Compute VM. classifyBootEvidence now only calls a touch confirmed-cold or confirmed-warm when the gap is at least CLOCK_SKEW_MARGIN_MS (2s, comfortably above plausible NTP skew and still small next to the 3.5-22s boot windows now sampled); anything closer is `unknown`, not a guess. The latency-based fallback (latencyMs >= 1000ms as proof of a cold start) is deleted outright — real samples have run 860-1598ms, straddling any fixed threshold, so a log read that fails is now `other` (inconclusive), never a guess. Also: a close is now decisive the moment it happens, so the sampling loop stops instead of running the rest of the budget for no change in verdict, and the script tracks its own MAX_RUN_MS (20 minutes) so a run that cannot reach bug-gone reports inconclusive instead of running into the CI jobs own kill. The jobs timeout-minutes goes from 10 to 30 to give that self-imposed stop room to finish and exit cleanly. Live evidence — four runs of this final version, raw stdout, no touched-up numbers: Stack streams-canary-ci-1784293693 (run 1 of that stack): waiting 60000ms before sample #0… sample #0: closed (502, 468ms) [logs: listening 2026-07-17T13:10:21.621Z, touch sent 2026-07-17T13:10:11.054Z (confirmed-cold)] close observed on sample #0; bug-present is already decided — skipping the remaining 13 samples. Cold-start close still present (1/1 first touches closed, 0 held, 0 never went cold) — PRO-217 not fixed; keep the PRO-219 backoff in createStreamsClient. Same stack (run 2, independent execution): waiting 60000ms before sample #0… sample #0: closed (502, 469ms) [logs: listening 2026-07-17T13:15:26.444Z, touch sent 2026-07-17T13:15:12.507Z (confirmed-cold)] close observed on sample #0; bug-present is already decided — skipping the remaining 13 samples. Cold-start close still present (1/1 first touches closed, 0 held, 0 never went cold) — PRO-217 not fixed; keep the PRO-219 backoff in createStreamsClient. Fresh stack streams-canary-ci-1784294211 (wall-clock timed with `time`, 96.86s total): waiting 60000ms before sample #0… sample #0: closed (502, 499ms) [logs: listening 2026-07-17T13:19:08.836Z, touch sent 2026-07-17T13:18:47.463Z (confirmed-cold)] close observed on sample #0; bug-present is already decided — skipping the remaining 13 samples. Cold-start close still present (1/1 first touches closed, 0 held, 0 never went cold) — PRO-217 not fixed; keep the PRO-219 backoff in createStreamsClient. Same stack (independent execution, exercising the log-read-timeout path): waiting 60000ms before sample #0… sample #0: closed (502, 473ms) [no listening line read within 30000ms — boot evidence unknown, not guessed] close observed on sample #0; bug-present is already decided — skipping the remaining 13 samples. Cold-start close still present (1/1 first touches closed, 0 held, 0 never went cold) — PRO-217 not fixed; keep the PRO-219 backoff in createStreamsClient. Every run correctly exits 0 (bug-present, green) — the expected outcome today, since PRO-217 is live. All three deployed stacks (plus one used while developing the sample-0-gap fix) were destroyed and scripts/ci-cleanup.ts confirmed zero leaked projects after each round. scripts/cold-start-canary-classify.test.ts extends to cover the margin- aware classifyBootEvidence, the sample-budget arithmetic, and the new inconclusive-below-budget verdict; each new/changed test verified to fail when its corresponding behavior is broken (checked by mutating the implementation and re-running, then reverting). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(gotchas): PRO-217 boot window, trigger, and removal guard match what we measured Three corrections, all from live sampling on 2026-07-17: The boot window is ~3-22s, not 3.5-8s. And boot length tracks how long the service sat idle: back-to-back promotions give ~1s boots where the close never appears (20 touches, all held), while 60s spacing gives the long boots where it reproduces readily. A probe that redeploys in a tight loop measures a window the bug does not live in. The reproduction advice was wrong. Waiting for the version to report running is too late: running flips ~1s after start, well before the app listens, so the touch lands warm. Race the promote call instead, space samples, and confirm coldness from the deployment log rather than from latency. Stopping a deployment is documented as a dead end: it 404s and never revives on a request. The removal guard now describes the sample budget: this bug is intermittent, so all-held in a small run is the expected outcome of a run that is too small, not evidence of a fix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * refactor(streams): the contract names its streams; handles own create and heal streamsContract was kind-only, so the stream lifecycle fell to application code: a STREAM constant, a memoized ensureStream, and a withStream heal wrapper around every call (Will's #92 review). streamsContract(defs) now names the streams it transports, each an optional streamDef() (untyped only this slice — no half-shipped event-schema parameter). The def map rides as the contract's __cmp, the same slot rpc's contract() uses for its function map; satisfies stays kind-only, since the Durable Streams server is schema-agnostic and checking __cmp at wiring time would be a guess. durableStreams(contract) hydrates to one StreamHandle per declared name; bare durableStreams() (dynamic names, the postgres() parity) hydrates to a StreamsClient whose whole surface is stream(name). createStreamsClient's object-of-closures becomes StreamsClient (the transport: URL, bearer header, per-stream write handles) and StreamHandle (one stream's name and ensure-create memo), per review direction. A handle creates its stream on first use, memoized, and heals a 404 by dropping the memo, re-creating, and retrying the failed operation once — unchanged from the proven-safe argument in review round 11 (a 404 proves nothing was applied, so the retry cannot duplicate an event, even an append). isStreamNotFound stops being exported: its only consumer was the app-side heal, which no longer exists. The streams() module's own exposed port can't carry any specific consumer's def map (different consumers may name different streams from the same module), so it's typed Contract<'streams', never> — never is the one Cmp that TypeScript will structurally accept in place of any more specific streamsContract(defs) requirement; a Record<string, StreamDef> alone does not typecheck there, since an index signature doesn't supply a literal required property. The wire-counted append tests (a 503 rejects after exactly one POST; five concurrent appends produce five POSTs) and the heal test move into this package and keep their teeth — re-verified red with the no-retry setting, the no-batch setting, and the heal body each removed in turn, then restored. IDEMPOTENT_BACKOFF and its PRO-219 comment are untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * refactor(streams): the example app loses all stream lifecycle code examples/streams/src/jobs/app.ts hand-rolled the platform's job: a STREAM name constant, a memoized ensureStream, and a withStream heal wrapper around every append/read/tail call (Will's #92 review — "why is the user's application doing this?"). The service now declares its one stream with streamsContract({ jobs: streamDef() }) and depends on durableStreams(jobLog); createJobsApp takes the hydrated StreamHandle directly and is left with exactly routes, the offset/timeout query-param mapping, and the 502-with-cause error mapping — zero lifecycle or wire-client knowledge. app.ts stays a plain Request -> Response function, independent of server.ts, so the integration test still drives it with no server or port. The "a stream lost from the durable tier heals" test moves out of jobs.integration.test.ts to the streams package as a StreamHandle test (committed separately) — the app has no lifecycle code left for it to exercise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs(streams): the package README/SCOPE follow the handle-owned lifecycle Both files still described the pre-redesign surface: streamsContract as a fixed kind-only value, durableStreams() as the only factory, and a StreamsClient whose create/append/read/tail all took a name argument. Updated to the named-contract, handle-owned-lifecycle shape: streamsContract(defs), streamDef(), durableStreams(contract) hydrating to one handle per name, bare durableStreams() for dynamic names via stream(name), and no create method on the handle (a handle creates its stream on first use). This is the package's own README/SCOPE, not docs/guides or the skill — those are the orchestrator's job at slice close-out, per this branch's established split between implementers and doc-writing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * chore(drive): as-built audit of the streams binding, on halt order The never-typed provider contract triggered a halt. The audit records what is actually checked at each moment for a streams edge, the type-level lie and its probe-proven honest replacement, the behavior changes that shipped beyond the design doc, and every unenforced invariant on the branch, with decisions R1-R6 for Will. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * fix(streams): the provider contract is the postgres pattern, not a never cast The streams() module port becomes Contract<"streams", StreamDefs> with an honest empty def map as its placeholder __cmp — structurally the same encoding postgresContract has always used for a kind-only contract. The consumer required type widens to match, which is the semantic truth (what a consumer requires of its provider is only "is a streams provider"); the literal per-stream handle typing is unchanged, since it comes from durableStreams(contract) generic parameter, not from the required type. Deletes the blindCast<never>(undefined) and the @internal/foundation dependency it existed for. Also classifies the two files this branch added (provider-params.ts, streams client.ts) in architecture.config.json — main gained a coverage check that fails the merge ref without them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> * docs: the design records the audit rulings; stale client-factory name gone The design doc now records what the audit surfaced and Will ruled on or defaulted: the postgres-pattern provider contract, implicit-only creation, read-creates semantics with its known dynamic-name cost, the contentType removal, the StreamDef marker, and Part A registry derivation. gotchas, the canary scripts, and the workflow stop naming createStreamsClient, which no longer exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> --------- Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bumps the dev-deps group with 4 updates in the / directory: @biomejs/biome, @clack/prompts, vitest and @durable-streams/server-conformance-tests.
Updates
@biomejs/biomefrom 2.5.2 to 2.5.3Release notes
Sourced from @biomejs/biome's releases.
... (truncated)
Changelog
Sourced from @biomejs/biome's changelog.
Commits
9e1999fchore: revert the revert0b0fd8dchore: revert0afa35aci: release (#10817)3447b2ffeat(useDomQuerySelector): add ignore option (#10835)Updates
@clack/promptsfrom 1.6.0 to 1.7.0Release notes
Sourced from @clack/prompts's releases.
Changelog
Sourced from @clack/prompts's changelog.
Commits
dc5bce8[ci] release (#575)4b24953chore: enable strict index checks (#582)8f1c380feat(prompts): add showInstructions opt-out (#574)06c16c7chore: update license copyright to Bombshell contributors (#572)Updates
vitestfrom 4.1.9 to 4.1.10Release notes
Sourced from vitest's releases.
Commits
db616d2chore: release v4.1.10 (#10718)bae52b5fix(vm): fix external module resolve error with deps optimizer query for enco...Updates
@durable-streams/server-conformance-testsfrom 0.2.3 to 0.3.5Release notes
Sourced from @durable-streams/server-conformance-tests's releases.
... (truncated)
Changelog
Sourced from @durable-streams/server-conformance-tests's changelog.
... (truncated)
Commits